﻿/*
 *   IRC - Internet Relay Chat, src/modules/user.c
 *   (C) 2005 The UnrealIRCd Team
 *
 *   See file AUTHORS in IRC package for additional names of
 *   the programmers.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "unrealircd.h"

CMD_FUNC(cmd_user);
int _nick_set(Client *client, char *nick_name);
bool checkPassword(D_User *d_user, char *password);
CMD_FUNC(cmd_phoneUser);
CMD_FUNC(cmd_emailUser);
CMD_FUNC(cmd_token);
#define MSG_USER 	"USER"	

ModuleHeader MOD_HEADER
  = {
	"user",
	"5.0",
	"command /user", 
	"UnrealIRCd Team",
	"unrealircd-5",
    };
MOD_TEST(){
    MARK_AS_OFFICIAL_MODULE(modinfo);
	EfunctionAdd(modinfo->handle, EFUNC_NICK_SET, _nick_set);
    return MOD_SUCCESS;
}
MOD_INIT()
{
	CommandAdd(modinfo->handle, MSG_USER, cmd_user, 4, CMD_UNLOGIN);
	CommandAdd(modinfo->handle, "PUSER", cmd_phoneUser, 2, CMD_UNLOGIN);
	CommandAdd(modinfo->handle, "EUSER", cmd_emailUser, 2, CMD_UNLOGIN);
	CommandAdd(modinfo->handle, "TUSER", cmd_token, 1, CMD_UNLOGIN);
	MARK_AS_OFFICIAL_MODULE(modinfo);
	return MOD_SUCCESS;
}

MOD_LOAD()
{
	return MOD_SUCCESS;
}

MOD_UNLOAD()
{
	return MOD_SUCCESS;
}

HTTPREQUESTFAILCALLBACK(wuehttp_token_getSelf_faile){
	sendnumeric(client, TOKENLOGINFAILE);
	return;
}


HTTPREQUESTSUCCESSCALLBACK(uehttp_token_getSelf_success){
	ircd_log(LOG_ERROR,"token get self:[%s]",requestData->response);
	cJSON *root = cJSON_Parse(requestData->response);
	if(!root){
		sendnumeric(client, TOKENLOGINFAILE);
		return;
	}
	cJSON *code = cJSON_GetObjectItem(root,"code");
	if(strcmp(code->valuestring,"0") != 0){
		sendnumeric(client, TOKENLOGINFAILE);
	}
	else{
		cJSON *json_data = cJSON_GetObjectItem(root,"data");
		int id;
		cJSON *cid = cJSON_GetObjectItem(json_data,"id");
		id = cid->valueint;
		if (!MyConnect(client) || IsServer(client)){
			cJSON_Delete(root);
			return;
		}
		cJSON *c_userName = cJSON_GetObjectItem(json_data,"userName");
		cJSON *c_realname = cJSON_GetObjectItem(json_data,"userNick");

		char *username=NULL;
		char *realname=NULL;
		if (MyConnect(client) && (client->local->listener->options & LISTENER_SERVERSONLY))
		{
			exit_client(client, NULL, "This port is for servers only");
			return;
		}
		ircd_log(LOG_ERROR,"puser username:%s,realname:%s",c_userName->valuestring,c_realname->valuestring);
		safe_strdup(username,c_userName->valuestring);
		safe_strdup(realname,c_realname->valuestring);
		
		if (strlen(username) > USERLEN)
			username[USERLEN] = '\0'; /* cut-off */

		make_user(client);

		/* set::modes-on-connect */
		client->umodes |= CONN_MODES;
		client->user->server = me_hash;
		strlcpy(client->info, realname, sizeof(client->info));
		strlcpy(client->user->username, username, USERLEN + 1);
		char nick_name[50+1];
		strlcpy(nick_name, realname, 51);
		client->user_id=id;
		int nick_result=_nick_set(client, nick_name);
		if (nick_result) {
			return;
		}
		userLoginLog(client->user_id,client->ip,client->mac);
		if (*client->name && is_handshake_finished(client))
		{
			/* NICK and no-spoof already received, now we have USER... */
			if (USE_BAN_VERSION && MyConnect(client))
			{
				sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
					me.name, client->name);
			}
			register_user(client, client->name, username, NULL, NULL, NULL);
			return;
		}
	}
	cJSON_Delete(root);
}

/**
 * 通过token登陆
 * parv[1] token
 * 
 */
CMD_FUNC(cmd_token){
	HttpRequestData * data = malloc(sizeof(HttpRequestData));
	char* url = "https://im.uesoft.com:8020/user/get/self";
	data->url=NULL;
	data->head_num = 1;
	data->head = safe_alloc(sizeof(char*));
	data->head[0] = NULL;
	size_t len = strlen(parv[1]);
	char *token = safe_alloc(len+10);
	memcpy(token,"token: ",7);
	memcpy(token + 7,parv[1],len);
	token[len+7]='\0';
	data->head[0] = token;
	safe_strdup(data->url,url);
	data->data=NULL;
	data->successfinishCallFun=uehttp_token_getSelf_success;
	data->failefinishCallFun=wuehttp_token_getSelf_faile;
	data->client=client;
	data->requestType = 0;
	data->head_num=1;
	add_httpRequestReady(data);
}

/** The USER command, together with NICK this will register a user.
 * As per UnrealIRCd 5 this command is only available to local clients.
 * Intraserver traffic is handled through the UID command.
 *	parv[1] = username
 *	parv[2] = client host name (ignored)
 *	parv[3] = 用户密码
 *	parv[4] = real name / gecos
 *
 * NOTE: Be advised that multiple USER messages are possible,
 *       hence, always check if a certain struct is already allocated... -- Syzop
 */
CMD_FUNC(cmd_user)
{
	char *username;
	char *realname;
	if (!IsAPPIdent(client)) {
		sendnumeric(client, ERR_WHITHOUTAPPIDENT);
		return;
	}
	if (!MyConnect(client) || IsServer(client))
		return;

	if (MyConnect(client) && (client->local->listener->options & LISTENER_SERVERSONLY))
	{
		exit_client(client, NULL, "This port is for servers only");
		return;
	}

	if ((parc < 5) || BadPtr(parv[4]))
	{
		sendnumeric(client, ERR_NEEDMOREPARAMS, "USER");
		return;
	}
	int findUser=0;
	int data_len = 0;
	D_User *d_user = queryImUserByUserName(parv[1], &data_len);
	if (data_len == 1 && checkPassword(d_user,parv[3])) {
		findUser=1;
	}
	if(!findUser){
		d_user = queryImUserByTel(parv[1],&data_len);
		if (data_len == 1 && checkPassword(d_user,parv[3])) {
			findUser=1;
		}
	}
	if(!findUser){
		d_user = queryImUserByEmail(parv[1],&data_len);
		if (data_len == 1 && checkPassword(d_user,parv[3])) {
			findUser=1;
		}
	}
	if(!findUser){
		sendnumeric(client, LOGINFAIL);
		return;
	}

	/* This cuts the username off at @, uh okay.. */
	if ((username = strchr(parv[1], '@')))
		*username = '\0';

	username = parv[1];
	realname = parv[4];
	
	if (strlen(username) > USERLEN)
		username[USERLEN] = '\0'; /* cut-off */

	make_user(client);

	/* set::modes-on-connect */
	client->umodes |= CONN_MODES;
	client->user->server = me_hash;
	strlcpy(client->info, realname, sizeof(client->info));
	strlcpy(client->user->username, username, USERLEN + 1);
	char nick_name[50+1];
	strlcpy(nick_name, d_user->user_name, 51);
	client->user_id=*(d_user->id);
	db_D_UserFree(d_user,data_len);
	int nick_result=_nick_set(client, nick_name);
	if (nick_result) {
		return;
	}
	userLoginLog(client->user_id,client->ip,client->mac);
	if (*client->name && is_handshake_finished(client))
	{
		/* NICK and no-spoof already received, now we have USER... */
		if (USE_BAN_VERSION && MyConnect(client))
		{
			sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
				me.name, client->name);
		}
		register_user(client, client->name, username, NULL, NULL, NULL);
		return;
	}
}

/**
 * 通过邮箱验证码登陆
 * parv[1] 邮箱号
 * parv[2] 邮箱验证码
 * 
 */
CMD_FUNC(cmd_emailUser){
	if (!IsAPPIdent(client)) {
		sendnumeric(client, ERR_WHITHOUTAPPIDENT);
		return;
	}
	if (parc != 3 || BadPtr(parv[2])) {
		sendnumeric(client, ERR_NEEDMOREPARAMS, "REGISTER");
		return;
	}
	if(!is_digist_string(parv[2],strlen(parv[2]))){
		sendnumeric(client, PARAMERROR, "Verification Code","not right value");
		return;
	}
	int userEmailExitNum=0;
	D_User  * d_user_email=queryImUserByEmail(parv[1],&userEmailExitNum);
	if(userEmailExitNum!=1){
		sendnumeric(client, PARAMERROR, "Verification Code","not right value");
		return;
	}

	if(!D_SMSVerificationCode(parv[1],parv[2],SMS_MODE_EMAIL,SMS_STATUS_LOGIN)){
		sendnumeric(client, SMSVALIDATIONFAILED);
		return;
	}
	char *username=NULL;
	char *realname=NULL;

	if (!MyConnect(client) || IsServer(client))
		return;

	if (MyConnect(client) && (client->local->listener->options & LISTENER_SERVERSONLY))
	{
		exit_client(client, NULL, "This port is for servers only");
		return;
	}
	ircd_log(LOG_ERROR,"puser username:%s,realname:%s",d_user_email->user_name,d_user_email->user_nick);
	safe_strdup(username,d_user_email->user_name);
	safe_strdup(realname,d_user_email->user_nick);
	
	if (strlen(username) > USERLEN)
		username[USERLEN] = '\0'; /* cut-off */

	make_user(client);

	/* set::modes-on-connect */
	client->umodes |= CONN_MODES;
	client->user->server = me_hash;
	strlcpy(client->info, realname, sizeof(client->info));
	strlcpy(client->user->username, username, USERLEN + 1);
	char nick_name[50+1];
	strlcpy(nick_name, d_user_email->user_name, 51);
	client->user_id=*(d_user_email->id);
	int nick_result=_nick_set(client, nick_name);
	if (nick_result) {
		return;
	}
	userLoginLog(client->user_id,client->ip,client->mac);
	if (*client->name && is_handshake_finished(client))
	{
		/* NICK and no-spoof already received, now we have USER... */
		if (USE_BAN_VERSION && MyConnect(client))
		{
			sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
				me.name, client->name);
		}
		register_user(client, client->name, username, NULL, NULL, NULL);
		return;
	}

}

/**
 * 通过手机验证码登陆
 * parv[1] 手机号
 * parv[2] 手机验证码
 * 
 */
CMD_FUNC(cmd_phoneUser){
	if (!IsAPPIdent(client)) {
		sendnumeric(client, ERR_WHITHOUTAPPIDENT);
		return;
	}
	if (parc != 3 || BadPtr(parv[2])) {
		sendnumeric(client, ERR_NEEDMOREPARAMS, "REGISTER");
		return;
	}
	if(!is_digist_string(parv[1],strlen(parv[1]))){
		sendnumeric(client, PARAMERROR, "tel","not right value");
		return;
	}
	if(!is_digist_string(parv[2],strlen(parv[2]))){
		sendnumeric(client, PARAMERROR, "Verification Code","not right value");
		return;
	}
	int userTelExitNum=0;
	D_User  * d_user_tel=queryImUserByTel(parv[1],&userTelExitNum);
	if(userTelExitNum!=1){
		sendnumeric(client, PARAMERROR, "Verification Code","not right value");
		return;
	}

	if(!D_SMSVerificationCode(parv[1],parv[2],SMS_MODE_PHONE,SMS_STATUS_LOGIN)){
		sendnumeric(client, SMSVALIDATIONFAILED);
		return;
	}
	char *username=NULL;
	char *realname=NULL;

	if (!MyConnect(client) || IsServer(client))
		return;

	if (MyConnect(client) && (client->local->listener->options & LISTENER_SERVERSONLY))
	{
		exit_client(client, NULL, "This port is for servers only");
		return;
	}
	ircd_log(LOG_ERROR,"puser username:%s,realname:%s",d_user_tel->user_name,d_user_tel->user_nick);
	safe_strdup(username,d_user_tel->user_name);
	safe_strdup(realname,d_user_tel->user_nick);
	
	if (strlen(username) > USERLEN)
		username[USERLEN] = '\0'; /* cut-off */

	make_user(client);

	/* set::modes-on-connect */
	client->umodes |= CONN_MODES;
	client->user->server = me_hash;
	strlcpy(client->info, realname, sizeof(client->info));
	strlcpy(client->user->username, username, USERLEN + 1);
	char nick_name[50+1];
	strlcpy(nick_name, d_user_tel->user_name, 51);
	client->user_id=*(d_user_tel->id);
	int nick_result=_nick_set(client, nick_name);
	if (nick_result) {
		return;
	}
	userLoginLog(client->user_id,client->ip,client->mac);
	if (*client->name && is_handshake_finished(client))
	{
		/* NICK and no-spoof already received, now we have USER... */
		if (USE_BAN_VERSION && MyConnect(client))
		{
			sendto_one(client, NULL, ":IRC!IRC@%s PRIVMSG %s :\1VERSION\1",
				me.name, client->name);
		}
		register_user(client, client->name, username, NULL, NULL, NULL);
		return;
	}

}

bool checkPassword(D_User *d_user,char *password) {

	char temp[200];
	int len1 = strlen(d_user->user_name);
	memcpy(temp,d_user->user_name,len1);
	int pos  = len1;
	memcpy(temp+pos,"|uesoft|",8);
	pos+= 8;
	int len2 = strlen(password);
	memcpy(temp+pos,password,len2);
	pos += len2;
	temp[pos] = '\0';
	char md5pwd[33];
	md5hash(md5pwd,temp,pos);
	ircd_log(LOG_ERROR,"checkPassword temp:%s,md5pwd:%s",temp,md5pwd);
	if (!strcasecmp(d_user->user_password, md5pwd)) {
		return true;
	}
	return false;
}
int _nick_set(Client *client, char *nick_name)
{
	TKL *tklban;
	int ishold;
	Client *acptr;
	char nick[NICKLEN + 2], descbuf[BUFSIZE];
	Membership *mp;
	long lastnick = 0l;
	int  differ = 1, update_watch = 1;
	unsigned char removemoder = (client->umodes & UMODE_REGNICK) ? 1 : 0;
	Hook *h;
	int i = 0;
	char *nickid = (IsUser(client) && *client->id) ? client->id : NULL;
	Client *cptr = client->direction; /* Pending a complete overhaul... (TODO) */


	/* Enforce minimum nick length */
	if (iConf.min_nick_length && !IsOper(client) && !IsULine(client) && strlen(nick_name) < iConf.min_nick_length)
	{
		snprintf(descbuf, sizeof descbuf, "A minimum length of %d chars is required", iConf.min_nick_length);
		sendnumeric(client, ERR_ERRONEUSNICKNAME, nick_name, descbuf);
		return -1;
	}

	/* Enforce maximum nick length */
	strlcpy(nick, nick_name, iConf.nick_length + 1);

	/* Check if this is a valid nick name */
	/*
	if (!do_nick_name(nick))
	{
		sendnumeric(client, ERR_ERRONEUSNICKNAME, nick_name, "Illegal characters");
		return -1;
	}
	*/

	if (!ValidatePermissionsForPath("immune:nick-flood", client, NULL, NULL, NULL))
		cptr->local->since += 3;	/* Nick-flood prot. -Donwulff */

	if ((acptr = find_client(nick, NULL)))
	{
		/* Shouldn't be possible since dot is disallowed: */
		if (IsServer(acptr))
		{
			sendnumeric(client, ERR_NICKNAMEINUSE, nick);
			return -1;
		}
		if (acptr == client)
		{
			/* New nick is exactly the same as the old nick? */
			if (!strcmp(acptr->name, nick))
				return -1;
			/* Changing cAsE */
			removemoder = 0;
		}
		else
			/* Collision with a nick of a session that is still in handshake */
			if (IsUnknown(acptr) && MyConnect(acptr))
			{
				/* Kill the other connection that is still in progress */
				SetKilled(acptr);
				exit_client(acptr, NULL, "Overridden");
			}
			else
			{
				sendnumeric(client, ERR_NICKNAMEINUSE, nick);
				return -1;	/* NICK message ignored */
			}
	}

	strlcpy(client->name, nick, sizeof(client->name));

	del_from_client_hash_table(client->name, client);
	if (update_watch && IsUser(client))
		hash_check_watch(client, RPL_LOGOFF);

	strlcpy(client->name, nick, sizeof(client->name));
	add_to_client_hash_table(nick, client);

	/* update fdlist --nenolod */
	snprintf(descbuf, sizeof(descbuf), "Client: %s", nick);
	fd_desc(client->local->fd, descbuf);

	if (update_watch && IsUser(client))
		hash_check_watch(client, RPL_LOGON);
	return 0;
}